package gs

import (
	"bytes"
	"encoding/json"
	"fmt"
	"regexp"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/fatih/color"
)

const ansi = "[\u001B\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[a-zA-Z\\d]*)*)?\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PRZcf-ntqry=><~]))"

var (
	ansicodeRE = regexp.MustCompile(ansi)
)

type Str string

func S(a any) Str {
	return Str(fmt.Sprint(a))
}

func (str Str) In(key any) bool {

	switch key.(type) {
	case string:
		return strings.Contains(str.String(), key.(string))
	case rune:
		return strings.Contains(str.String(), string(key.(rune)))
	case byte:
		return bytes.Contains(str.Bytes(), []byte{key.(byte)})
	case Str:
		return strings.Contains(str.String(), key.(Str).String())
	case int, float32, float64:
		return strings.Contains(str.String(), S(key).String())
	default:
		fmt.Println(key)
		return false
	}
}

/*
Last:

	negIndex  < 0 , like -1:
		a.Last(-2) ==  a[:a.Len() - 2]
*/
func (str Str) Last(negIndex int) Str {
	if negIndex > 0 {
		panic("must negIndex < 0")
	}
	return str[:str.Len()+negIndex]
}

func (str Str) Strs() Strs {
	return Strs{str}
}
func (str Str) String() string {
	return string(str)
}

func (str Str) Repeat(i int) Str {
	return Str(strings.Repeat(str.Str(), i))
}

func (str Str) Reverse() Str {
	ds := []rune{}
	for _, c := range str {
		ds = append([]rune{c}, ds...)
	}
	return Str(string(ds))
}

func (str Str) Str() string {
	return string(str)
}

func (str Str) TryInt() int {
	i, err := strconv.Atoi(str.Str())
	if err != nil {
		panic(err)
	}
	return i
}

func (str Str) TryLong() int64 {
	i, err := strconv.ParseInt(str.Str(), 10, 64)
	if err != nil {
		panic(err)
	}
	return i
}

func (str Str) Unquote() (Str, error) {
	e, err := strconv.Unquote(str.String())
	if err != nil {
		str.Color("red").Println("Panic")
		// panic(err)
		return "", err
	}
	return Str(e), nil
}

func (str Str) ToFirstUpper() Str {
	return Str(strings.ToUpper(string(str[0])) + str[1:].String())
}

func (str Str) Bytes() []byte {
	return []byte(str)
}

func (str Str) Len(as_bytes ...bool) int {
	if as_bytes != nil && as_bytes[0] {
		return len(str)
	}
	return utf8.RuneCountInString(str.Str())
}

func (str Str) Cut(length int) (substrings List[Str]) {
	// Decode the string into a slice of Unicode code points
	runes := []rune(str)

	// Loop over the code points and cut the string into substrings
	for i := 0; i < len(runes); i += length {
		end := i + length
		if end > len(runes) {
			end = len(runes)
		}
		substrings = append(substrings, Str(runes[i:end]))
	}

	return substrings
}

func (str Str) Printable(removeANSICODE ...bool) Str {
	if removeANSICODE != nil && removeANSICODE[0] {

		c := Str(ansicodeRE.ReplaceAllString(str.Str(), ""))
		clean := strings.Map(func(r rune) rune {
			if unicode.IsPrint(r) {
				return r
			}
			return -1
		}, c.Str())
		return Str(clean)

	} else {
		clean := strings.Map(func(r rune) rune {
			if unicode.IsPrint(r) {
				return r
			}
			return -1
		}, str.Str())
		return Str(clean)

	}
}

func (str Str) F(args ...interface{}) Str {
	return Str(fmt.Sprintf(str.String(), args...))
}

func (str Str) Print(pre ...interface{}) Str {
	if str == "" {
		return str
	}
	if pre != nil {
		preRes := fmt.Sprint(pre...)
		fmt.Println(color.New(color.Bold, color.FgWhite).Sprint("[", preRes, "] ") + str.String())
	} else {
		fmt.Print(str.String())
	}
	return str
}

func (str Str) Println(pre ...interface{}) Str {
	if str == "" {
		return str
	}
	if pre != nil {
		preRes := fmt.Sprint(pre...)
		fmt.Println(color.New(color.Bold, color.FgWhite).Sprint("[", preRes, "] ") + str.String())
	} else {
		fmt.Println(color.New(color.Bold, color.FgWhite).Sprint("[+] ") + str.String())
	}
	return str
}

func (str Str) EndPrintln(args ...interface{}) Str {
	if str == "" {
		return str
	}
	if args != nil {
		preRes := fmt.Sprint(args...)
		fmt.Println(str.String() + "\n" + color.New(color.Bold, color.FgWhite).Sprint("[", preRes, "] "))
	} else {
		fmt.Println(str.String() + "\n" + color.New(color.Bold, color.FgWhite).Sprint("[+] "))
	}
	return str
}

func (str Str) Color(colorcodes ...string) Str {
	if str == "" {
		return str
	}
	w := color.New(color.Bold).SprintFunc()
	if colorcodes != nil {
		e := str
		attrs := []color.Attribute{}
		for _, col_code := range colorcodes {
			// fmt.Println(col_code)
			switch col_code {
			case "background", "R", "reverse":
				attrs = append(attrs, color.BgWhite)
			case "green", "g":
				attrs = append(attrs, color.FgGreen)
			case "red", "r":
				attrs = append(attrs, color.FgRed)
			case "blue", "b":
				attrs = append(attrs, color.FgBlue)
			case "yellow", "y":
				attrs = append(attrs, color.FgYellow)
			case "bold", "B":
				attrs = append(attrs, color.Bold)
			case "magenta", "m":
				attrs = append(attrs, color.FgMagenta)
			case "underline", "U":
				attrs = append(attrs, color.Underline)
			case "flash", "F":
				attrs = append(attrs, color.BlinkRapid)
			case "cyan", "c":
				attrs = append(attrs, color.FgCyan)
			}
		}
		// fmt.Println(attrs, len(attrs))
		e = Str(color.New(attrs...).Sprint(e))
		return e
	} else {
		return Str(w(str))
	}
}

func (str Str) Index(i Str) (start int, end int) {
	start = strings.Index(str.String(), i.String())
	end = start + i.Len()
	return
}

type Loc [2]int

func (str Str) IndexAll(i Str) (locs List[Loc]) {
	s := str
	offset := 0
	for {
		start := strings.Index(s.String(), i.String())
		if start > -1 {
			end := start + i.Len()
			locs = locs.Add(Loc{offset + start, offset + end})
			offset += end
			s = s[end:]
		} else {
			break
		}
	}

	return
}

func (str Str) RindexAll(i Str) (locs List[Loc]) {
	s := str
	for {
		ret, rst := s.Reverse().Index(i.Reverse())
		// end = start + i.Len()
		start := i.Len() - rst - i.Len()
		if start > -1 {
			end := i.Len() - ret
			locs = locs.Add(Loc{start, end})
			if start == 0 {
				break
			}
			// offset += start
			s = s[:start]
		} else {
			break
		}
	}

	return
}

func (str Str) Rindex(i Str) (start int, end int) {
	ret, rst := str.Reverse().Index(i.Reverse())
	// end = start + i.Len()
	start = i.Len() - rst - i.Len()
	end = i.Len() - ret
	return
}

func (str Str) MStr(mark bool) MStr {
	return MStr{
		Str:  str,
		Mark: mark,
	}
}

func (str Str) Mark(middle Str) (ms MStrs) {
	pre := str
	if middle.Len() == 0 {
		return ms.Add(str.MStr(false))
	}
	for {
		st, et := pre.Index(middle)
		if st != -1 {
			preS := str[:st]

			ms = ms.Add(preS.MStr(false))
			ms = ms.Add(middle.MStr(true))
			pre = str[et:]
		} else {
			ms = ms.Add(pre.MStr(false))
			break
		}
	}
	return
}

func (str Str) Add(i any) Str {
	return Str(str.String() + fmt.Sprint(i))
}

func (str Str) Trim() Str {
	return Str(strings.TrimSpace(str.String()))
}

func (str Str) StartsWith(pre string, orOtherPres ...string) bool {
	if orOtherPres == nil {
		return strings.HasPrefix(str.String(), pre)

	} else {
		if strings.HasPrefix(str.String(), pre) {
			return true
		}

		for _, otherPre := range orOtherPres {
			if strings.HasPrefix(str.String(), otherPre) {
				return true
			}
		}
	}
	return false
}

func (str Str) EndsWith(suffix string, orOtherSuffixs ...string) bool {
	if orOtherSuffixs == nil {
		return strings.HasSuffix(str.String(), suffix)
	} else {
		if strings.HasSuffix(str.String(), suffix) {
			return true
		}

		for _, otherSuffix := range orOtherSuffixs {
			if strings.HasSuffix(str.String(), otherSuffix) {
				return true
			}
		}
	}
	return false
}

func (str Str) IsFirstUpper() bool {
	return Str("ABCDEFGHIJKLMNOPQRSTUVWXYZ").In(str[0])
}

func (str Str) SplitSkiptQuote(sep string, n ...int) (strs Strs) {
	quoted := false
	quotes := ""
	maches := ""
	// mached := false
	// machall := false

	seps := Str(sep)

	for _, item := range strings.FieldsFunc(str.Str(), func(r rune) bool {
		if r == '`' && (quotes == "" || quotes == "`") {
			quoted = !quoted
			quotes = "`"
		} else if r == '"' && (quotes == "" || quotes == `"`) {
			quoted = !quoted
			quotes = "\""
		}

		if !quoted {
			maches += string(r)
			if !strings.HasPrefix(sep, maches) {
				maches = string(r)
			}

			if maches == sep {
				maches = ""
				return true
			}
		}
		return false

	}) {
		if len(sep) > 1 && Str(item).EndsWith(seps[:seps.Len()-1].Str()) {
			strs = strs.Add(Str(item[:len(item)-seps.Len()+1]))
		} else {
			strs = strs.Add(Str(item))
		}
	}

	// strs.Every(EachPrint("+", "g"))
	if n != nil && n[0] < 0 {
		return strs
	} else if n == nil {
		return strs
	} else {
		if n[0] < strs.Len() {
			nstrs := strs[:n[0]]
			nstrs = nstrs.Add(strs[n[0]:].Join(seps))
			return nstrs
		} else {
			return strs
		}
	}
}

func (str Str) o(sep string, n ...int) (strs Strs) {
	// str.Color("r").Println("split before")

	st, _ := str.Index(`"`)
	sst, _ := str.Index("`")

	if st < 0 && sst < 0 {
		return str.Split(sep, n...)
	} else {
		nstr := str
		oneline := Str("")
		i := 0
		for nstr.Len() > 0 {
			i += 1
			if i%100 == 0 {
				fmt.Print(i, "\r")
			}
			firstHitSt, firstHitEd := nstr.Index(`"`)
			firstHitQSt, firstHitQEd := nstr.Index("`")
			nFirstHitST, nFirstHitED := nstr.Index("\n")
			quote := `"`
			if sst < st {
				firstHitEd = firstHitQEd
			}

			if (nFirstHitST > firstHitSt || nFirstHitST > firstHitQSt) && (firstHitSt+firstHitQSt > -2) {
				oneline += nstr[:firstHitEd]
				secondHitST, secondHitED := nstr[firstHitEd:].Index(Str(quote))
				if secondHitST < 0 {
					oneline += nstr[firstHitEd:]
					break
				} else {
					oneline += nstr[firstHitEd : firstHitEd+secondHitED]
					realst, realed := nstr[firstHitEd+secondHitED:].Index("\n")
					if realst < 0 {
						oneline += nstr[firstHitEd+secondHitED:]
						break
					} else {
						oneline += nstr[firstHitEd+secondHitED : firstHitEd+secondHitED+realst]
						strs = strs.Add(oneline)
						oneline = Str("")
						nstr = nstr[firstHitEd+secondHitED+realed:]
					}
				}

			} else {
				// realst, realed := nstr.Index("\n")
				if nFirstHitST < 0 {
					oneline = nstr
					break
				}
				oneline = nstr[:nFirstHitST]
				strs = strs.Add(oneline)
				oneline = Str("")
				nstr = nstr[nFirstHitED:]
			}
		}
		if oneline != "" {
			strs = strs.Add(oneline)
		}
	}
	return
	nstr, keys := str.QuoteMark()
	return nstr.Split(sep, n...).With(func(ix int, i Str) Str {
		return i.Format(keys.MapString())
	})
}

func (str Str) EveryLine(lineCall func(lineno int, line Str)) Str {
	str.Split("\n").Every(func(ix int, str Str) {
		lineCall(ix, str)
	})
	return str
}

func (str Str) Count(key string) int {
	return strings.Count(str.String(), key)
}

func (str Str) Json() Dict[any] {
	e := make(Dict[any])
	err := json.Unmarshal(str.Bytes(), &e)
	if err != nil {
		panic(err)
	}
	return e
}

func (str Str) Upper() Str {
	return Str(strings.ToUpper(str.Str()))
}

func (str Str) Slice(first, end int) Str {
	if first < 0 {
		first = str.Len() + first
	}
	if end < 0 {
		end = str.Len() + end
	}
	return str[first:end]
}
